package com.xtt._02_guava;

import com.google.common.collect.*;
import org.junit.Test;
import java.util.*;

/**
 * @Author xtt.
 * @Description:
 * @Date:Create in 2017/9/14 0014 下午 18:42
 * @Modify By:
 */
public class GuavaCollections {
    /*http://ifeve.com/google-guava-newcollectiontypes/*/

    /**
     * Multiset
     */
    @Test
    public void testMultiSet() {
        /*  HashMultiset
            TreeMultiset（如果comparator支持的话）
            LinkedHashMultiset
            ConcurrentHashMultiset
            ImmutableMap
            ...
            */
        //可计算每个元素的个数
        String[] arr = {"a","b","c","d","e","f","g","h","a","b","c"};
        Multiset<String> multiset = HashMultiset.create();
        Arrays.stream(arr).forEach((str) -> multiset.add(str));
        System.out.println("count:"+multiset.count("a"));//给定元素在Multiset中的计数
        System.out.println("elementSet:"+multiset.elementSet());//Multiset中不重复元素的集合，类型为Set<E>
        System.out.println("entrySet:"+multiset.entrySet());//和Map的entrySet类似，返回Set<Multiset.Entry<E>>，其中包含的Entry支持getElement()和getCount()方法
        System.out.println("add:"+multiset.add("a",4));//增加给定元素在Multiset中的计数
        System.out.println("count:"+multiset.count("a"));
        System.out.println("remove:"+multiset.remove("c",1));//增加给定元素在Multiset中的计数
        System.out.println("count:"+multiset.count("c"));
        System.out.println("setCount:"+multiset.setCount("c",10));//设置给定元素在Multiset中的计数，不可以为负数
        System.out.println("count:"+multiset.count("c"));
        /*  count:2
            elementSet:[a, b, c, d, e, f, g, h]
            entrySet:[a x 2, b x 2, c x 2, d, e, f, g, h]
            add:2
            count:6
            remove:1
            count:1
            setCount:2
            count:10
            */
    }

    /**
     * SortedMultiSet
     */
    @Test
    public void testSortedMultiset(){
        /*SortedMultiset是Multiset 接口的变种，它支持高效地获取指定范围的子集。
        比方说，你可以用 latencies.subMultiset(0,BoundType.CLOSED, 100, BoundType.OPEN).size()
        来统计你的站点中延迟在100毫秒以内的访问，然后把这个值和latencies.size()相比，
        以获取这个延迟水平在总体访问中的比例。*/
        SortedMultiset sortedMultiset = TreeMultiset.create();
        Integer[] lats = {1,2,3,4,5,6,100,200,300,400,500,600,700};
        Arrays.stream(lats).forEach(val -> sortedMultiset.add(val));
        System.out.println(sortedMultiset.subMultiset(100,BoundType.CLOSED,500,BoundType.OPEN));//[100, 200, 300, 400]
        System.out.println(sortedMultiset.subMultiset(100,BoundType.CLOSED,500,BoundType.CLOSED));//[100, 200, 300, 400, 500]
    }

    /**
     * MultiMap
     */
    @Test
    public void testMultiMap() {
        //每个有经验的Java程序员都在某处实现过Map<K, List<V>>或Map<K, Set<V>>，
        /*可以用两种方式思考Multimap的概念：”键-单个值映射”的集合：
        a -> 1 a -> 2 a ->4 b -> 3 c -> 5
        或者”键-值集合映射”的映射：
        a -> [1, 2, 4] b -> 3 c -> 5
        一般来说，Multimap接口应该用第一种方式看待，但asMap()视图返回Map<K, Collection<V>>，
        让你可以按另一种方式看待Multimap。重要的是，不会有任何键映射到空集合：一个键要么至少到一个值，
        要么根本就不在Multimap中。
        很少会直接使用Multimap接口，更多时候你会用ListMultimap或SetMultimap接口，
        它们分别把键映射到List或Set。*/
        String[] arr = {"a=1","a=2","a=3","b=1","b=2","c=1"};
        ListMultimap<String,String> listMultimap = ArrayListMultimap.create();
        Arrays.stream(arr).map(x -> x.split("=")).forEach(x -> listMultimap.put(x[0],x[1]));
        List<String> aList = listMultimap.get("a");
        System.out.println(aList);
        Map<String, Collection<String>> stringCollectionMap = listMultimap.asMap();
        System.out.println(stringCollectionMap);
        listMultimap.replaceValues("c", Arrays.asList("4","5","6"));
        System.out.println(listMultimap.get("c"));
        /*
        *   [1, 2, 3]
            {a=[1, 2, 3], b=[1, 2], c=[1]}
            [4, 5, 6]
        */
    }

    /**
     * BiMap
     */
    @Test
    public void testBiMap() {
        /*传统上，实现键值对的双向映射需要维护两个单独的map，并保持它们间的同步。但这种方式很容易出错
         如：
        Map<String, Integer> nameToId = Maps.newHashMap();
        Map<Integer, String> idToName = Maps.newHashMap();
        nameToId.put("Bob", 42);
        idToName.put(42, "Bob");

        *BiMap<K, V>是特殊的Map：
        可以用 inverse()反转BiMap<K, V>的键值映射
        保证值是唯一的，因此 values()返回Set而不是普通的Collection
        在BiMap中，如果你想把键映射到已经存在的值，会抛出IllegalArgumentException异常。
        如果对特定值，你想要强制替换它的键，请使用 BiMap.forcePut(key, value)。
        * */
        BiMap<String,Integer> biMap = HashBiMap.create();
        biMap.put("one",1);
        biMap.put("two",2);
        biMap.put("three",3);
        System.out.println(biMap);

        BiMap<Integer, String> inverse = biMap.inverse();
        System.out.println(inverse);

        biMap.forcePut("three",4);
        System.out.println(biMap);
    }

    /**
     * Table
     */
    @Test
    public void testTable(){
        /*
        * 当你想使用多个键做索引的时候,可能会用类似Map<FirstName, Map<LastName, Person>>的实现
        *
        *   HashBasedTable：本质上用HashMap<R, HashMap<C, V>>实现；
            TreeBasedTable：本质上用TreeMap<R, TreeMap<C,V>>实现；
            ImmutableTable：本质上用ImmutableMap<R, ImmutableMap<C, V>>实现；注：ImmutableTable对稀疏或密集的数据集都有优化。
            ArrayTable：要求在构造时就指定行和列的大小，本质上由一个二维数组实现，以提升访问速度和密集Table的内存利用率。
            ArrayTable与其他Table的工作原理有点不同，请参见Javadoc了解详情。
        * */
        Table<String,String,String > table = HashBasedTable.create();
        table.put("a","1","zs");
        table.put("a","2","ls");
        table.put("b","1","ww");
        table.put("b","2","zl");

        Map<String, String> rowA = table.row("a");
        System.out.println(rowA);
        Map<String, String> column1 = table.column("1");
        System.out.println(column1);

        String a2 = table.get("a", "2");
        System.out.println(a2);
        /*
        *   {1=zs, 2=ls}
            {a=zs, b=ww}
            ls*/
    }

    /**
     * ClassToInstanceMap
     */
    @Test
    public void testClassToInstanceMap() {
        /*
        * ClassToInstanceMap是一种特殊的Map：它的键是类型，而值是符合键所指类型的对象。
        为了扩展Map接口，ClassToInstanceMap额外声明了两个方法：
        T getInstance(Class<T>) 和T putInstance(Class<T>, T)，
        从而避免强制类型转换，同时保证了类型安全。*/
        /*对于ClassToInstanceMap，Guava提供了两种有用的实现：
        MutableClassToInstanceMap和 ImmutableClassToInstanceMap。*/

        ClassToInstanceMap<Number> numberDefaults=MutableClassToInstanceMap.create();
        numberDefaults.putInstance(Integer.class, Integer.valueOf(0));
    }

    /**
     * RangeSet
     */
    @Test
    public void testRangeSet() {
        //RangeSet类是用来存储一些不为空的也不相交的范围的数据结构。
        //假如需要向RangeSet的对象中加入一个新的范围，那么任何相交的部分都会被合并起来，所有的空范围都会被忽略

        //实现了RangeSet接口的类有ImmutableRangeSet和TreeRangeSet

        //例：取不重复可用IP区间IP段 http://blog.csdn.net/linbilin_/article/details/53410061
        RangeSet rangeSet = TreeRangeSet.create();
        rangeSet.add(Range.closed(1, 10));
        System.out.println(rangeSet);
        rangeSet.add(Range.closedOpen(11, 15));
        System.out.println(rangeSet);
        rangeSet.add(Range.open(15, 20));
        System.out.println(rangeSet);
        rangeSet.add(Range.openClosed(0, 0));
        System.out.println(rangeSet);
        rangeSet.remove(Range.open(5, 10));
        System.out.println(rangeSet);
        RangeSet complement = rangeSet.complement();//返回RangeSet的补集视图。complement也是RangeSet类型,包含了不相连的、非空的区间。
        System.out.println(complement);
        Set set = rangeSet.asRanges();
        System.out.println(set);//用Set<Range<C>>表现RangeSet，这样可以遍历其中的Range
        //subRangeSet(Range<C>) //返回RangeSet与给定Range的交集视图。这扩展了传统排序集合中的headSet、subSet和tailSet操作。
        boolean contains = rangeSet.contains(10);//RangeSet最基本的操作，判断RangeSet中是否有任何区间包含给定元素
        Range range = rangeSet.rangeContaining(10);//返回包含给定元素的区间；若没有这样的区间，则返回null。
        boolean encloses = rangeSet.encloses(range);//简单明了，判断RangeSet中是否有任何区间包括给定区间。
        Range span = rangeSet.span();//返回包括RangeSet中所有区间的最小区间
        System.out.println(span);//[1..20)

/*
        *   [[1..10]]
            [[1..10], [11..15)]
            [[1..10], [11..15), (15..20)]
            [[1..10], [11..15), (15..20)]
            [[1..5], [10..10], [11..15), (15..20)]
            [(-∞..1), (5..10), (10..11), [15..15], [20..+∞)]
            [[1..5], [10..10], [11..15), (15..20)]
        * */
    }
    /**
     * RangeMap
     */
    @Test
    public void testRangeMap() {
        /*RangeMap描述了”不相交的、非空的区间”到特定值的映射。
        和RangeSet不同，RangeMap不会合并相邻的映射，即便相邻的区间映射到相同的值。*/
        RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
        rangeMap.put(Range.closed(1, 10), "aaa");
        System.out.println(rangeMap);
        rangeMap.put(Range.open(3, 6), "bbb");
        System.out.println(rangeMap);
        rangeMap.put(Range.openClosed(10, 20), "aaa");
        System.out.println(rangeMap);
        rangeMap.put(Range.closed(20, 20), "aaa");
        System.out.println(rangeMap);
        rangeMap.remove(Range.closed(5, 11));
        System.out.println(rangeMap);
        /*  [[1..3]=aaa, (3..6)=bbb, [6..10]=aaa]
            [[1..3]=aaa, (3..6)=bbb, [6..10]=aaa, (10..20]=aaa]
            [[1..3]=aaa, (3..6)=bbb, [6..10]=aaa, (10..20)=aaa, [20..20]=aaa]
            [[1..3]=aaa, (3..5)=bbb, (11..20)=aaa, [20..20]=aaa]*/
        //遍历
        Map<Range<Integer>, String> rangeStringMap = rangeMap.asMapOfRanges();
        Set<Map.Entry<Range<Integer>, String>> entries = rangeStringMap.entrySet();
        Iterator<Map.Entry<Range<Integer>, String>> iterator = entries.iterator();
        while(iterator.hasNext()){
            Map.Entry<Range<Integer>, String> next = iterator.next();
            System.out.println(next.getKey() + "\t" + next.getValue());
        }
        /*  [1..3]	aaa
            (3..5)	bbb
            (11..20)	aaa
            [20..20]	aaa
            */
    }
}
